<?php

/**
 * 安心站长axzz.cn，一个追求技术创造者。让网站、小程序、APP牛起来……
 *
 * cighsen02 3651872575
 *
 * Create at: 2024.05.01
 * Update at: 2025.08.26
*/

namespace Twoir\Service\Member;

use Exception;
use Twoir\Service\BaseMember;
use Twoir\System\Model\EnewsMember;
use Twoir\System\Model\EnewsMemberGroup;
use Twoir\System\Model\EnewsPublic;

/**
 * 会员注册
*/
class Register extends BaseMember
{
    // 允许进入字段
    const ALLOW = ['username','password','repassword','email','phno','groupid','key'];
    // 验证码key名称
    const REG_KEY_NAME = 'checkregkey';

    public function __construct()
    {
        global $public_r;
        // 关闭注册
        if(isset($public_r['register_ok']) && $public_r['register_ok']) throw new Exception('暂时关闭注册', 1200);
        // 验证时间段允许操作
        $this->checkTimeCloseDo('reg', $public_r['timeclosedo'], $public_r['timeclose']);
        // 验证IP
        $this->checkAccessDoIp('register');
        // 验证来源
        $this->checkCanPostUrl($public_r['canposturl']);

        parent::__construct(false);
        // 您已登陆，退出后重试
        if(!empty($this->userInfo())) throw new Exception('您已登陆，退出后重试', 7200);
    }

    /**
     * 邮箱注册
     *
     * @param array $add
     * @return array
    */
    public function email($add)
    {
        global $public_r;
        // 参数过滤
        $add = $this->paramFilter($add);

        return [];
    }

    /**
     * 用户名注册
     *
     * @param array $add
     * @return array
     *
     * @throws Exception
    */
    public function userName(array $add): array
    {
        global $public_r, $level_r, $ecms_config;
        // 参数过滤
        $add 		= $this->paramFilter($add);

        $username	= isset($add['username']) && $add['username'] ? trim($add['username']) : null;
        $password	= isset($add['password']) && $add['password'] ? trim($add['password']) : null;
        $username	= RepPostVar($username);
        $password	= RepPostVar($password);
        $email		= isset($add['email']) && $add['email'] ? addslashes(RepPostStr($add['email'])) : null;
        $email		= RepPostVar($email);
        $phno       = isset($add['phno']) && $add['phno'] ? RepPostVar($add['phno']) : null;

        if(!$username) throw new Exception('用户名不能为空', 9200);

        if(!$password) throw new Exception('密码不能为空', 9200);

        if (3 != $public_r['regmustef']) {

            // 邮箱必填
            if(1 == $public_r['regmustef']){

                if(!$email) throw new Exception('邮箱不能为空', 9200);
            }
            // 手机必填
            elseif(2 == $public_r['regmustef']){

                if(!$phno) throw new Exception('手机不能为空', 9200);
            }
            // 邮箱和手机必填
            else {

                if(!$email || !$phno) throw new Exception('手机和邮箱不能为空', 9200);
            }
        }

        if (!isset($add['repassword']) && empty($add['repassword'])) throw new Exception('重复密码不能为空', 9200);
        // 验证码
        if($public_r['regkey_ok']){

            if (!isset($add['key']) || empty($add['key'])) throw new Exception('验证码不能为空', 9200);

            $this->checkShowKey(static::REG_KEY_NAME, $add['key'], 1);
        }
        // 会员组
        $groupid = (int)($add['groupid'] ?? $this->returnMemberDefGroupid());

        $this->checkMemberGroupCanReg($groupid);

        // 认证码
        if(isset($level_r[$groupid]['regps']) && $level_r[$groupid]['regps'])
        {
            if($level_r[$groupid]['regps'] !== $add['mg_regps'])
            {
                throw new Exception('认证码错误', 9200);
            }
        }

        $regip		= egetip();
        $eipf       = egetipfrom();
        $regipport	= egetipport();

        extract(EnewsPublic::getOne(['id' => 1], ['min_userlen','max_userlen','min_passlen','max_passlen','regretime','regclosewords','regemailonly','regphonly']));

        $userlen = strlen($username);
        if($userlen < $min_userlen || $userlen > $max_userlen) throw new Exception('用户名长度过长或过短', 9200);

        // 密码字数
        $passlen = strlen($password);
        if($passlen < $min_passlen || $passlen > $max_passlen) throw new Exception('密码长度过长或过短', 9200);

        if($add['repassword'] !== $password) throw new Exception('两次密码不一致', 9200);

        if(!chemail($email)) throw new Exception('邮箱格式错误', 9200);

        if(!echphno($phno)) throw new Exception('手机格式错误', 9200);

        if(str_contains($username, '|') || str_contains($username, '*')) throw new Exception('请勿使用“|”、“*”字符', 9200);

        // 同一IP注册
        $this->checkIpRegTime($regip, $regretime);
        // 保留用户
        $this->toCheckCloseWord($username, $regclosewords, 'RegHaveCloseword');
        $username = RepPostStr($username);
        // 重复用户
        if(EnewsMember::getCount(['username' => $username]) > 0) throw new Exception('此用户名已经存在，请重输', 9200);
        // 重复邮箱
        if($regemailonly && EnewsMember::getCount(['email' => $email]) > 0) throw new Exception('此邮箱已经被注册', 9200);
        // 重复手机
        if($regphonly && EnewsMember::getCount(['phno' => $phno]) > 0) throw new Exception('此邮箱已经被注册', 9200);
        // 注册时间
        $lasttime		= time();
        $registertime	= $this->returnAddMemberRegtime();
        $rnd			= make_password(EcmsRandInt(20,36));// 产生随机密码
        $rndt           = make_password(EcmsRandInt(20,32));
        $userkey		= eReturnMemberUserKey();
        // 密码
        $truepassword	= $password;
        $salt			= $this->returnMemberSalt();
        $password		= eDoMemberPw($password, $salt);
        // 审核
        $checked		= $this->returnGroupChecked($groupid);
        if($checked && $public_r['regacttype']) $checked = 0;
        $checked		= (int)$checked;
        // 新增数据
        $insert 		= [
            'username'		=> $username,
            'password'		=> $password,
            'rnd'			=> $rnd,
            'email'			=> $email,
            'registertime'	=> $registertime,
            'groupid'		=> $groupid,
            'userfen'		=> $public_r['reggetfen'],
            'userdate'		=> 0,
            'money'			=> 0,
            'zgroupid'		=> 0,
            'havemsg'		=> 0,
            'checked'		=> $checked,
            'salt'			=> $salt,
            'userkey'		=> $userkey,
            'isot'          => 1,
            'phno'          => $phno,
            'mustdo'        => 0,
            'rndt'          => $rndt,

            'spacestyleid'	=> ReturnGroupSpaceStyleid($groupid),
            'regip'			=> $regip,
            'lasttime'		=> $lasttime,
            'lastip'		=> $regip,
            'loginnum'		=> 1,
            'regipport'		=> $regipport,
            'lastipport'	=> $regipport,
            'eipf'          => $eipf
        ];

        if(0 < $userid = EnewsMember::insert($insert)) {

            // 清空验证码
            $this->emptyShowKey(static::REG_KEY_NAME);

            $logincookie = $ecms_config['member']['regcookietime'] ? $lasttime + $ecms_config['member']['regcookietime'] : 0;

            esetcookie("mlusername", $username, $logincookie);
            esetcookie("mluserid", $userid, $logincookie);
            esetcookie("mlgroupid", $groupid, $logincookie);
            esetcookie("mlrnd", $rnd, $logincookie);
            esetcookie("mlrndt", $rndt, $logincookie);
            // 验证符
            $this->qGetLoginAuthstr($userid, $username, $rnd, $groupid, $logincookie);
            esetcookie("returnurl", '');

            unset($insert['password'], $insert['userkey'], $insert['salt']);

            return ['userid' => $userid] + $insert;
        }

        return [];
    }

    /**
     * 手机注册，在做之前请在主表增加phone字段
     *
     * @param array $add
     * @return array
    */
    public function phone($add)
    {
        global $public_r;

        // 参数过滤
        $add = $this->paramFilter($add);

        return [];
    }

    /**
     * 返回是否审核
     *
     * @param int $groupid
    */
    protected function returnGroupChecked(int $groupid): int
    {
        global $level_r;

        return isset($level_r[$groupid]['regchecked']) && 1 == $level_r[$groupid]['regchecked'] ? 0 : 1;
    }

    /**
     * 验证包含字符
     *
     * @param string $word
     * @param string $closestr
     * @param string $mess
     *
     * @throws Exception
    */
    protected function toCheckCloseWord(string $word, string $closestr, string $mess)
    {
        if($closestr && $closestr !== '|'){

            $checkr	    = explode('|', $closestr);
            $ckcount    = count($checkr);

            for($i = 0; $i < $ckcount; $i++){

                if(isset($checkr[$i])){

                    if(str_contains($checkr[$i],'##')){// 多字

                        $morer = explode('##', $checkr[$i]);
                        if(false !== stristr($word, $morer[0]) && false !== stristr($word, $morer[1])){

                            throw new Exception('用户名包含禁用字符', 9200);
                        }
                    }
                    else{

                        if(false !== stristr($word, $checkr[$i])) throw new Exception('用户名包含禁用字符', 9200);
                    }
                }
            }
        }
    }

    /**
     * 验证注册时间
     *
     * @param string $ip
     * @param int $time
     * @return string
     *
     * @throws Exception
    */
    protected function checkIpRegTime(string $ip, int $time)
    {
        if(empty($time)) return '';

        if(empty($user = EnewsMember::getOne(['regip' => $ip], ['userid'], '', 'userid DESC')))	return '';

        if(empty($user = EnewsMember::getOne(['userid' => $user['userid']], ['userid', 'registertime']))) return '';

        if(time() - $this->returnMemberIntRegtime($user['registertime']) <= $time * 3600) throw new Exception('同一IP不能重复注册', 9200);

        return '';
    }

    /**
     * 返回SALT
     *
     * @return string
    */
    protected function returnMemberSalt(): string
    {
        global $ecms_config;

        if(isset($ecms_config['member']['saltnum']) && $ecms_config['member']['saltnum'] < 0)
        {
            $n = 0 - $ecms_config['member']['saltnum'];

            return make_password(EcmsRandInt($n, 12));
        }

        return make_password($ecms_config['member']['saltnum']);
    }

    /**
     * 返回当前注册时间
     *
     * @param int
    */
    protected function returnAddMemberRegtime(): int
    {
        global $ecms_config;

        return empty($ecms_config['member']['regtimetype']) ? date('Y-m-d H:i:s') : time();
    }

    /**
     * 返回注册时间(int)
     *
     * @param string
     * @return int
    */
    protected function returnMemberIntRegtime(string $regtime): int
    {
        global $ecms_config;

        return empty($ecms_config['member']['regtimetype']) ? to_time($regtime) : $regtime;
    }

    /**
     * 检查用户组是否可注册
     *
     * @param int $groupid
     *
     * @throws Exception
    */
    protected function checkMemberGroupCanReg(int $groupid)
    {
        if($groupid && 0 === EnewsMemberGroup::getCount(['groupid' => $groupid, 'canreg' => 1])){

            throw new Exception('当前会员不允许注册', 9200);
        }
    }

    /**
     * 验证来源
     *
     * @param string $canposturl
     *
     * @throws Exception
    */
    protected function checkCanPostUrl(string $canposturl)
    {
        if($canposturl){

            $r		= explode("\r\n", $canposturl);
            $count	= count($r);
            $b 		= false;
            for($i = 0; $i < $count; $i++){

                if(isset($_SERVER['HTTP_REFERER']) && false !== stristr($_SERVER['HTTP_REFERER'], $r[$i])){

                    $b = true;
                    break;
                }
            }

            if(!$b) throw new Exception('请从网站提交数据', 1200);
        }
    }

    /**
     * 验证提交IP
     *
     * @param string $do
     *
     * @throws Exception
    */
    protected function checkAccessDoIp(string $do)
    {
        $pr = EnewsPublic::getOne(['id' => 1], ['opendoip', 'closedoip', 'doiptype']);

        if(isset($pr['doiptype']) && false !== strstr($pr['doiptype'], ',' . $do . ','))
        {
            $userip = egetip();
            // 允许IP
            if(isset($pr['opendoip']) && $pr['opendoip']){

                foreach(explode("\n", $pr['opendoip']) as $ctrlip){

                    if(preg_match("/^(" . preg_quote(($ctrlip = trim($ctrlip)), '/') . ")/", $userip)){

                        throw new Exception('当前ip已经禁止注册', 1200);
                    }
                }
            }
            // 禁止IP
            if(isset($pr['closedoip']) && $pr['closedoip']){

                foreach(explode("\n", $pr['closedoip']) as $ctrlip){

                    if(preg_match("/^(" . preg_quote(($ctrlip = trim($ctrlip)), '/') . ")/", $userip)){

                        throw new Exception('当前ip已经禁止注册', 1200);
                    }
                }
            }
        }
    }

    /**
     * 验证操作时间
     *
     * @param string $do
     * @param string $timeclosedo
     * @param string $timeclose
     *
     * @throws Exception
    */
    protected function checkTimeCloseDo(string $do, string $timeclosedo, string $timeclose)
    {
        if(false !== stristr($timeclosedo, ',' . $do . ',')) throw new Exception('当前区间关闭注册', 1200);

        if(str_contains($timeclose, ',' . date('G') . ',')) throw new Exception('当前时间关闭注册', 1200);
    }
}
